#include "scene.h"

#include <cassert>

#include "material.h"
#include "plane.h"
#include "sphere.h"
#include "camera.h"
#include "light.h"

namespace ray_tracing
{
	scene_t::scene_t()
		: scene_object_t(nullptr, nullptr)
		, rendering_camera(nullptr)
	{
	}

	scene_t::~scene_t()
	{
		for(auto obj : objects)
			delete obj;

		for(auto obj : materials)
			delete obj;
		
		for(auto obj : lights)
			delete obj;

		for(auto& camera : cameras)
			delete camera.second;
	}

	collision::collision_info_t scene_t::intersect_with(const ray3f& ray, hit_param param)
	{
		using namespace collision;
		//very insufficient. 
		collision_info_t min_collision_info = make_miss_collinfo();
		float tmin = std::numeric_limits<float>::max();

		for(scene_object_t* obj : get_objects())
		{
			const collision_info_t& collision_info = obj->intersect_with(ray, param);

			if(collision_info.status != collision_status::miss)
			{
				if(collision_info.t < tmin)
				{
					tmin = collision_info.t;
					min_collision_info = collision_info;
					min_collision_info.user_data = obj;
				}

				if(param == hit_param::first_hit)
					break;
			}
		}

		return min_collision_info;
	}

	camera_t* scene_t::get_rendering_camera() const
	{
		return rendering_camera;
	}

	camera_t* scene_t::get_camera(const std::wstring& name) const
	{
		auto it = cameras.find(name);

		if(it != cameras.end())
		{
			return it->second;
		}

		return nullptr;
	}

	void scene_t::set_rendering_camera(camera_t* camera)
	{
		rendering_camera = camera;
	}

	camera_t* scene_t::add_camera(const std::wstring& name, float in_angle, float in_N, const pnt3f& in_pos,
								  const vec3f& in_up, const vec3f& in_look_at)
	{
		assert(!name.empty() && in_angle > 0.f && in_N > 0.f && length(in_up) > 0.);

		auto it = cameras.find(name);

		if(it == cameras.end())
		{
			auto camera = new camera_t(in_angle, in_N, in_pos, in_up, in_look_at);

			if(cameras.empty())
				set_rendering_camera(camera);

			cameras[name] = camera;

			return camera;
		}

		assert(get_rendering_camera() != nullptr);

		return nullptr;
	}
	
	const scene_nodes_t& scene_t::get_objects() const
	{
		return objects;
	}

	const lights_type& scene_t::get_lights() const
	{
		return lights;
	}

	scene_object_t* scene_t::add_sphere(const pnt3f& origin, float R, material_t* in_material)
	{
		auto new_sphere = new sphere_t( this, origin, R, in_material);
		objects.push_back( new_sphere );
		return new_sphere;
	}

	scene_object_t* scene_t::add_plane(const vec3f& normal, float D, material_t* in_material)
	{
		auto new_plane = new plane_t( this, normal, D, in_material);
		objects.push_back( new_plane );
		return new_plane;
	}

	light_t* scene_t::add_light(const pnt3f& origin, const vec3f& in_color)
	{
		auto new_light = new light_t(origin, in_color);
		lights.push_back( new_light );
		return new_light;
	}

	material_t* scene_t::add_material(const vec3f& in_ambient_color, 
									  const vec3f& in_diffuse_color, const vec3f& in_specular_color, 
			float in_specular_power, float in_refraction_index, float in_reflection_contribution, float in_refraction_contribution)
	{
		auto new_material = new material_t(in_ambient_color, in_diffuse_color, in_specular_color, in_specular_power, 
			in_refraction_index, in_reflection_contribution, in_refraction_contribution);
		materials.push_back( new_material );
		return new_material;
	}
}